package org.g4studio.core.mvc.xstruts.validator;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.WrapDynaBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.g4studio.core.mvc.xstruts.action.ActionMapping;

/**
 * <p>
 * Struts <i>validator</i> <code>ActionForm</code> backed by either a
 * <code>DynaBean</code> or POJO JavaBean.
 * </p>
 * 
 * <p>
 * Passing a POJO JavaBean to the constructor will automatically create an
 * associated <code>WrapDynaBean</code>. One use for this would be to migrate
 * <i>view</i> objects from an existing system which, for the usual reasons,
 * can't be changed to extend <ActionForm</code>.
 * </p>
 * 
 * <p>
 * This form is based on the standard struts <code>ValidatorForm</code> for use
 * with the <i>Validator</i> framework and validates either using the
 * <i>name</i> from the Struts <code>ActionMapping</code> or the
 * <code>ActionMapping</code>'s path depending on whether
 * <code>pathValidation</code> is <code>true</code> or <code>false</code>.
 * </p>
 * 
 * <p>
 * <b>Note</b>: WrapDynaBean is NOT serializable. If you use this class with a
 * WrapDynaBean (as described above), you should not store your form in session
 * scope.
 * </p>
 */
public class BeanValidatorForm extends ValidatorForm implements DynaBean, Serializable {
	/**
	 * Commons Logging
	 */
	protected static Log logger = LogFactory.getLog(BeanValidatorForm.class);

	/**
	 * The <code>DynaBean</code> that this ActionForm is backed by.
	 */
	protected DynaBean dynaBean;

	/**
	 * Indicates whether the ActionMapping's path should be used for the
	 * validation key.
	 */
	protected boolean pathValidation = false;

	// ------------------- Constructor ----------------------------------

	/**
	 * Construct a new <code>BeanValidatorForm</code> with the specified bean.
	 */
	public BeanValidatorForm(Object bean) {
		if (bean instanceof DynaBean) {
			dynaBean = (DynaBean) bean;
		} else {
			dynaBean = new WrapDynaBean(bean);
		}
	}

	// ------------------- Protected Methods ----------------------------------

	/**
	 * <p>
	 * Set whether this form should validate based on the
	 * <code>ActionMapping</code>'s path.
	 * </p>
	 */
	protected void setPathValidation(boolean pathValidation) {
		this.pathValidation = pathValidation;
	}

	/**
	 * <p>
	 * Indicates whether this form should validate based on the
	 * <code>ActionMapping</code>'s path.
	 * </p>
	 */
	protected boolean isPathValidation() {
		return pathValidation;
	}

	// ------------------- Public Methods ----------------------------------

	/**
	 * <p>
	 * Return the <code>DynaBean</code> that this <code>ActionForm</code> is
	 * backed by.
	 * </p>
	 */
	public DynaBean getDynaBean() {
		return dynaBean;
	}

	/**
	 * <p>
	 * Return the <code>Bean</code> that this <code>ActionForm</code> is backed
	 * by.
	 * </p>
	 * 
	 * <p>
	 * If the <code>DynaBean</code> is a <code>WrapDynaBean</code> type then
	 * this method returns the 'Wrapped' POJO bean associated with it. If you
	 * require the actual <code>WrapDynaBean</code> then use the
	 * <code>getDynaBean()</code> method.
	 * </p>
	 */
	public Object getInstance() {
		if (dynaBean instanceof WrapDynaBean) {
			return ((WrapDynaBean) dynaBean).getInstance();
		}

		return dynaBean;
	}

	/**
	 * <p>
	 * Return the size of an indexed or mapped property.
	 * </p>
	 */
	public int size(String name) {
		Object value = dynaBean.get(name);

		if (value == null) {
			return 0;
		}

		if (value instanceof Map) {
			return ((Map) value).size();
		}

		if (value instanceof List) {
			return ((List) value).size();
		}

		if ((value.getClass().isArray())) {
			return Array.getLength(value);
		}

		return 0;
	}

	// ------------------- ValidatorForm Methods
	// ----------------------------------

	/**
	 * Returns the Validation key
	 * 
	 * @param mapping
	 *            The mapping used to select this instance
	 * @param request
	 *            The servlet request we are processing
	 * @return validation key to use
	 */
	public String getValidationKey(ActionMapping mapping, HttpServletRequest request) {
		String validationKey = null;

		if (isPathValidation()) {
			// Get the path replacing any slashes by underscore
			validationKey = mapping.getPath();

			// Remove any leading slash
			if (validationKey.charAt(0) == '/') {
				validationKey = validationKey.substring(1);
			}

			// Replace any slashes by underscore
			if (validationKey.indexOf("/") > 0) {
				validationKey = validationKey.replace('/', '_');
			}
		} else {
			validationKey = mapping.getAttribute();
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Validating ActionForm '" + mapping.getName() + "' using key '" + validationKey
					+ "' for mapping '" + mapping.getPath() + "'");
		}

		return validationKey;
	}

	// ------------------- DynaBean Methods ----------------------------------

	/**
	 * Return the <code>DynaClass</code> instance that describes the set of
	 * properties available for this DynaBean.
	 */
	public DynaClass getDynaClass() {
		return dynaBean.getDynaClass();
	}

	/**
	 * Return the value of a simple property with the specified name.
	 * 
	 * @param name
	 *            Name of the property whose value is to be retrieved
	 */
	public Object get(String name) {
		return dynaBean.get(name);
	}

	/**
	 * Return the value of an indexed property with the specified name.
	 * 
	 * @param name
	 *            Name of the property whose value is to be retrieved
	 * @param index
	 *            Index of the value to be retrieved
	 */
	public Object get(String name, int index) {
		return dynaBean.get(name, index);
	}

	/**
	 * Return the value of a mapped property with the specified name, or
	 * <code>null</code> if there is no value for the specified key.
	 * 
	 * @param name
	 *            Name of the property whose value is to be retrieved
	 * @param key
	 *            Key of the value to be retrieved
	 */
	public Object get(String name, String key) {
		return dynaBean.get(name, key);
	}

	/**
	 * Set the value of a simple property with the specified name.
	 * 
	 * @param name
	 *            Name of the property whose value is to be set
	 * @param value
	 *            Value to which this property is to be set
	 */
	public void set(String name, Object value) {
		// Set the page number (for validator)
		if ("page".equals(name)) {
			if (value == null) {
				page = 0;
			} else if (value instanceof Integer) {
				page = ((Integer) value).intValue();
			} else {
				try {
					page = ((Integer) ConvertUtils.convert(value.toString(), Integer.class)).intValue();
				} catch (Exception ignore) {
					page = 0;
				}
			}
		}

		dynaBean.set(name, value);
	}

	/**
	 * Set the value of an indexed property with the specified name.
	 * 
	 * @param name
	 *            Name of the property whose value is to be set
	 * @param index
	 *            Index of the property to be set
	 * @param value
	 *            Value to which this property is to be set
	 */
	public void set(String name, int index, Object value) {
		dynaBean.set(name, index, value);
	}

	/**
	 * Set the value of a mapped property with the specified name.
	 * 
	 * @param name
	 *            Name of the property whose value is to be set
	 * @param key
	 *            Key of the property to be set
	 * @param value
	 *            Value to which this property is to be set
	 */
	public void set(String name, String key, Object value) {
		dynaBean.set(name, key, value);
	}

	/**
	 * Does the specified mapped property contain a value for the specified key
	 * value?
	 * 
	 * @param name
	 *            Name of the property to check
	 * @param key
	 *            Name of the key to check
	 */
	public boolean contains(String name, String key) {
		return dynaBean.contains(name, key);
	}

	/**
	 * Remove any existing value for the specified key on the specified mapped
	 * property.
	 * 
	 * @param name
	 *            Name of the property for which a value is to be removed
	 * @param key
	 *            Key of the value to be removed
	 */
	public void remove(String name, String key) {
		dynaBean.remove(name, key);
	}
}
